let FlagOfset = 0;

const DONOTHING = 1 << FlagOfset++;
const BREAK_FLOW = 1 << FlagOfset++;
const LOCAL_TRANSFORM = 1 << FlagOfset++;
const WORLD_TRANSFORM = 1 << FlagOfset++;
const TRANSFORM = LOCAL_TRANSFORM | WORLD_TRANSFORM;
const UPDATE_RENDER_DATA = 1 << FlagOfset++;
const OPACITY = 1 << FlagOfset++;
const COLOR = 1 << FlagOfset++;
const OPACITY_COLOR = OPACITY | COLOR;
const RENDER = 1 << FlagOfset++;
const CHILDREN = 1 << FlagOfset++;
const POST_RENDER = 1 << FlagOfset++;
const FINAL = 1 << FlagOfset++;

let _batcher, _forward;
let _cullingMask = 0;

function RenderFlow() {
    this._func = init;
    this._next = null;
}

let _proto = RenderFlow.prototype;
_proto._doNothing = function () {
};

_proto._localTransform = function (node) {
    node._updateLocalMatrix();
    node._renderFlag &= ~LOCAL_TRANSFORM;
    this._next._func(node);
};

_proto._worldTransform = function (node) {
    _batcher.worldMatDirty++;

    let t = node._matrix;
    let trs = node._trs;
    let tm = t.m;
    tm[12] = trs[0];
    tm[13] = trs[1];
    tm[14] = trs[2];

    node._mulMat(node._worldMatrix, node._parent._worldMatrix, t);
    node._renderFlag &= ~WORLD_TRANSFORM;
    this._next._func(node);

    _batcher.worldMatDirty--;
};

_proto._opacity = function (node) {
    _batcher.parentOpacityDirty++;

    node._renderFlag &= ~OPACITY;
    this._next._func(node);

    _batcher.parentOpacityDirty--;
};

_proto._color = function (node) {
    let comp = node._renderComponent;
    if (comp) {
        comp._updateColor();
    }

    node._renderFlag &= ~COLOR;
    this._next._func(node);
};

_proto._updateRenderData = function (node) {
    let comp = node._renderComponent;
    comp._assembler.updateRenderData(comp);
    node._renderFlag &= ~UPDATE_RENDER_DATA;
    this._next._func(node);
};

_proto._render = function (node) {
    let comp = node._renderComponent;
    comp._checkBacth(_batcher, node._cullingMask);
    comp._assembler.fillBuffers(comp, _batcher);
    this._next._func(node);
};
function NodeTreeDepthSort1(myBatcher, container, c) {
    if (!c) return;
    let depth = 0;
    if (myBatcher.m_customBatchMap) {
        depth = myBatcher.m_customBatchMap[c.name];
        if (!depth) {
            depth = myBatcher.m_customBatchList.length;
        }
    }
    container[depth] = container[depth] || [];
    //c._worldDirtyFlag = 0;
    if (c._activeInHierarchy && c._opacity != 0 && !c.isHide) {
        container[depth].push(c);
        let _children = c._children;
        let count = _children.length;
        let d = 0;
        for (let i = 0; i < count; i++) {
            let child = _children[i];
            NodeTreeDepthSort1(myBatcher, container, child);
        }
    }
}
function NodeTreeDepthSort2(myBatcher, container, c, depth) {
    container[depth] = container[depth] || [];
    if (c._activeInHierarchy && c._opacity != 0 && !c.isHide) {
        container[depth].push(c);
        let _children = c._children;
        let count = _children.length;
        for (let i = 0; i < count; i++) {
            depth = depth + 1;
            let child = _children[i];
            NodeTreeDepthSort2(myBatcher, container, child, depth);
        }
    }
}
/**
 * @method BactherForNode
 * @param {cc.Node} root 
 * @description ?????|??????D??
 */
function BatcherForNode(root) {
    let container = [];
    let myBatcher = root.getComponent('MyBatcher');
    if (!myBatcher.m_isAutoBatch) {
        // let NodeTreeDepthSort = function (c) {
        //     if (!c) return;
        //     let depth = 0;
        //     if (myBatcher.m_customBatchMap) {
        //         depth = myBatcher.m_customBatchMap[c.name];
        //         if (!depth) {
        //             depth = myBatcher.m_customBatchList.length;
        //         }
        //     }
        //     container[depth] = container[depth] || [];
        //     //c._worldDirtyFlag = 0;
        //     if (c._activeInHierarchy && c._opacity != 0 && !c.isHide ) {
        //         container[depth].push(c);
        //         let _children = c._children;
        //         let count = _children.length;
        //         let d = 0;
        //         for (let i = 0; i < count; i++) {
        //             let child = _children[i];
        //             NodeTreeDepthSort(child);
        //         }
        //     }
        // }
        let children = root._children;
        let children_count = children.length;
        for (let i = 0; i < children_count; i++) {
            NodeTreeDepthSort1(myBatcher, container, children[i]);
        }
    } else {
        let depth = 0;
        // let NodeTreeDepthSort = function (c) {
        //     container[depth] = container[depth] || [];
        //     if (c._activeInHierarchy && c._opacity != 0 && !c.isHide ) {
        //         container[depth].push(c);
        //         let _children = c._children;
        //         let count = _children.length;
        //         for (let i = 0; i < count; i++) {
        //             depth = depth + 1;
        //             let child = _children[i];
        //             NodeTreeDepthSort(child);
        //         }
        //     }
        //     //c._worldDirtyFlag = 0;
        // }
        let children = root._children;
        let children_count = children.length;
        for (let i = 0; i < children_count; i++) {
            depth = 0;
            NodeTreeDepthSort2(myBatcher, container, children[i], depth);
        }
    }
    let ret = [];
    for (let dep = 0; dep < container.length; dep++) {
        Array.prototype.push.apply(ret, container[dep]);
    }
    return ret;
}
//mike
_proto._children = function (node) {
    let cullingMask = _cullingMask;
    let batcher = _batcher;

    let parentOpacity = batcher.parentOpacity;
    let opacity = (batcher.parentOpacity *= (node._opacity / 255));

    let worldTransformFlag = batcher.worldMatDirty ? WORLD_TRANSFORM : 0;
    let worldOpacityFlag = batcher.parentOpacityDirty ? OPACITY_COLOR : 0;
    let worldDirtyFlag = worldTransformFlag | worldOpacityFlag;

    //fixed by tinker
    let isBatch = false;
    if (!(node instanceof cc.Scene)) {
        let myBatcher = node.getComponent('MyBatcher');
        isBatch = myBatcher && myBatcher.enabled ? true : false;
    }
    //isBatch = false;
    if (isBatch) {
        node._batcherOpactity = opacity;
        let children = BatcherForNode(node);
        for (let i = 0, l = children.length; i < l; i++) {
            let c = children[i];
            if (!c) continue;
            //缩放
            if (c._localMatDirty == 2) {
                c._children.forEach(child => {
                    child.setLocalDirty(2)
                })
            }
            // if (c._parent) {
            //     c._renderFlag |= c._parent._worldDirtyFlag || 0;
            // }
            // if (c._renderFlag & WORLD_TRANSFORM) {
            //     c._worldDirtyFlag |= WORLD_TRANSFORM;
            // }
            // if (c._renderFlag & OPACITY) {
            //     c._worldDirtyFlag |= OPACITY;
            // }
            // if (c._renderFlag & COLOR) {
            //     c._worldDirtyFlag |= COLOR;
            // }
            //c._renderFlag |= worldDirtyFlag;
            //c._renderFlag |= WORLD_TRANSFORM;
            //console.log('mike');
            c._renderFlag &= ~CHILDREN;
            if (!c._activeInHierarchy || c._opacity === 0) continue;
            let colorVal = c._color._val;
            //c._color._fastSetA(c._opacity * opacity);
            c._batcherOpactity = c.parent._batcherOpactity * (c._opacity / 255)
            c._color._fastSetA(c._opacity * c.parent._batcherOpactity);
            flows[c._renderFlag]._func(c);
            c._color._val = colorVal;
        }
    } else {
        let children = node._children;
        for (let i = 0, l = children.length; i < l; i++) {
            let c = children[i];
            // Advance the modification of the flag to avoid node attribute modification is invalid when opacity === 0.
            c._renderFlag |= worldDirtyFlag;
            if (!c._activeInHierarchy || c._opacity === 0 || c.isHide) continue;

            _cullingMask = c._cullingMask = c.groupIndex === 0 ? cullingMask : 1 << c.groupIndex;

            // TODO: Maybe has better way to implement cascade opacity
            let colorVal = c._color._val;
            c._color._fastSetA(c._opacity * opacity);
            flows[c._renderFlag]._func(c);
            c._color._val = colorVal;
        }
    }
    batcher.parentOpacity = parentOpacity;

    this._next._func(node);
};

_proto._postRender = function (node) {
    let comp = node._renderComponent;
    comp._checkBacth(_batcher, node._cullingMask);
    comp._assembler.postFillBuffers(comp, _batcher);
    this._next._func(node);
};

const EMPTY_FLOW = new RenderFlow();
EMPTY_FLOW._func = EMPTY_FLOW._doNothing;
EMPTY_FLOW._next = EMPTY_FLOW;

let flows = {};

function createFlow(flag, next) {
    let flow = new RenderFlow();
    flow._next = next || EMPTY_FLOW;

    switch (flag) {
        case DONOTHING:
            flow._func = flow._doNothing;
            break;
        case BREAK_FLOW:
            flow._func = flow._doNothing;
            break;
        case LOCAL_TRANSFORM:
            flow._func = flow._localTransform;
            break;
        case WORLD_TRANSFORM:
            flow._func = flow._worldTransform;
            break;
        case OPACITY:
            flow._func = flow._opacity;
            break;
        case COLOR:
            flow._func = flow._color;
            break;
        case UPDATE_RENDER_DATA:
            flow._func = flow._updateRenderData;
            break;
        case RENDER:
            flow._func = flow._render;
            break;
        case CHILDREN:
            flow._func = flow._children;
            break;
        case POST_RENDER:
            flow._func = flow._postRender;
            break;
    }

    return flow;
}

function getFlow(flag) {
    let flow = null;
    let tFlag = FINAL;
    while (tFlag > 0) {
        if (tFlag & flag)
            flow = createFlow(tFlag, flow);
        tFlag = tFlag >> 1;
    }
    return flow;
}

// 
function init(node) {
    let flag = node._renderFlag;
    let r = flows[flag] = getFlow(flag);
    r._func(node);
}

RenderFlow.flows = flows;
RenderFlow.createFlow = createFlow;

RenderFlow.visitRootNode = function (rootNode) {
    _cullingMask = 1 << rootNode.groupIndex;

    if (rootNode._renderFlag & WORLD_TRANSFORM) {
        _batcher.worldMatDirty++;
        rootNode._calculWorldMatrix();
        rootNode._renderFlag &= ~WORLD_TRANSFORM;

        flows[rootNode._renderFlag]._func(rootNode);

        _batcher.worldMatDirty--;
    }
    else {
        flows[rootNode._renderFlag]._func(rootNode);
    }
};

RenderFlow.render = function (scene, dt) {
    _batcher.reset();
    _batcher.walking = true;

    RenderFlow.visitRootNode(scene);

    _batcher.terminate();
    _batcher.walking = false;

    _forward.render(_batcher._renderScene, dt);
};

RenderFlow.init = function (batcher, forwardRenderer) {
    _batcher = batcher;
    _forward = forwardRenderer;

    flows[0] = EMPTY_FLOW;
    for (let i = 1; i < FINAL; i++) {
        flows[i] = new RenderFlow();
    }
};

RenderFlow.getBachther = function () {
    return _batcher;
};

RenderFlow.FLAG_DONOTHING = DONOTHING;
RenderFlow.FLAG_BREAK_FLOW = BREAK_FLOW;
RenderFlow.FLAG_LOCAL_TRANSFORM = LOCAL_TRANSFORM;
RenderFlow.FLAG_WORLD_TRANSFORM = WORLD_TRANSFORM;
RenderFlow.FLAG_TRANSFORM = TRANSFORM;
RenderFlow.FLAG_OPACITY = OPACITY;
RenderFlow.FLAG_COLOR = COLOR;
RenderFlow.FLAG_OPACITY_COLOR = OPACITY_COLOR;
RenderFlow.FLAG_UPDATE_RENDER_DATA = UPDATE_RENDER_DATA;
RenderFlow.FLAG_RENDER = RENDER;
RenderFlow.FLAG_CHILDREN = CHILDREN;
RenderFlow.FLAG_POST_RENDER = POST_RENDER;
RenderFlow.FLAG_FINAL = FINAL;

module.exports = cc.RenderFlow = RenderFlow;
